/**
 * 
 */
package truerefactor.metrics;

import java.util.List;

import truerefactor.graph.Accessibility;
import truerefactor.graph.AttributeNode;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.MethodNode;

/**
 * CS - An implementation of the Class Size metric
 * 
 * @author Isaac Griffith
 */
public class CS extends Metric {

    /**
     * Constructs a new CS Metric instance
     */
    public CS()
    {
        this.shortName = "CS";
        this.name = "Class Size";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see truerefactor.metrics.Metric#measure(truerefactor.graph.CodeGraph)
     */
    @Override
    public double measure(CodeGraph graph)
    {
        // Measures the relative size of a class based on the number of methods
        // and attributes that the class contains

        double mCS = 0;
        double tCS = 0;

        List<ClassNode> classes = graph.getClasses();
        for (ClassNode cNode : classes)
        {
            tCS += measure(cNode);
        }

        mCS = tCS / classes.size();
        return mCS;
    }

    /**
     * Provides a static measurement of the provided ClassNode
     * 
     * @param cNode
     *            ClassNode to be measured
     * @return CS measurement for the provided ClassNode
     */
    public static double measure(ClassNode cNode)
    {
        List<CodeNode> methods = cNode.getMethods();
        List<CodeNode> attrs = cNode.getAttributes();

        int pubMethods = 0;
        int priMethods = 0;
        int pubAttrs = 0;
        int priAttrs = 0;

        for (CodeNode node : methods)
        {
            if (node instanceof MethodNode)
            {
                MethodNode method = (MethodNode) node;

                switch (method.getAccessibility())
                {
                case Public:
                    pubMethods++;
                    break;
                default:
                    priMethods++;
                    break;
                }
            }
        }

        for (CodeNode node : attrs)
        {
            if (node instanceof MethodNode)
            {
                AttributeNode attr = (AttributeNode) node;

                switch (attr.getAccessibility())
                {
                case Public:
                    pubAttrs++;
                    break;
                default:
                    priAttrs++;
                    break;
                }
            }
        }
        return (1.5 * (pubAttrs + pubMethods) + (priAttrs + priMethods));
    }

    /**
     * Returns a list of all methods associated with the provided start class
     * 
     * @param nodes
     *            List of MehtodNodes representing all inherited and contained
     *            methods of the class represented by the provided ClassNode
     * @param start
     *            the ClassNode to find all methods for
     */
    private static void getAllMethods(List<MethodNode> nodes, ClassNode start)
    {
        List<ClassNode> superClasses = start.getParentClasses();

        ClassNode superClass = null;
        for (CodeNode node : superClasses)
        {
            if (node instanceof ClassNode)
            {
                ClassNode cNode = (ClassNode) node;
                if (!cNode.isInterface())
                {
                    superClass = cNode;
                    break;
                }
            }
        }

        if (superClass == null)
            return;
        else
        {
            List<CodeNode> methods = superClass.getMethods();
            for (CodeNode node : methods)
            {
                if (node instanceof MethodNode)
                {
                    MethodNode method = (MethodNode) node;

                    if (!method.isAbstract() && !method.getAccessibility().equals(Accessibility.Private))
                    {
                        nodes.add(method);
                    }
                }
            }
        }

        getAllMethods(nodes, superClass);
    }

    /**
     * Returns all attributes associated with the provided class node
     * 
     * @param nodes
     *            List of AttributeNodes which the provided classnode either
     *            contains or inherits
     * @param start
     *            the class node for which all attributes are required.
     */
    private static void getAllAttributes(List<AttributeNode> nodes, ClassNode start)
    {
        List<ClassNode> superClasses = start.getParentClasses();

        ClassNode superClass = null;
        for (CodeNode node : superClasses)
        {
            if (node instanceof ClassNode)
            {
                ClassNode cNode = (ClassNode) node;
                if (!cNode.isInterface())
                {
                    superClass = cNode;
                    break;
                }
            }
        }

        if (superClass == null)
            return;
        else
        {
            List<CodeNode> attrs = superClass.getAttributes();
            for (CodeNode node : attrs)
            {
                if (node instanceof MethodNode)
                {
                    AttributeNode field = (AttributeNode) node;

                    if (!field.getAccessibility().equals(Accessibility.Private))
                    {
                        nodes.add(field);
                    }
                }
            }
        }

        getAllAttributes(nodes, superClass);
    }
}
